const path = require('path');
const uuid = require('uuid');
const koa = require('koa');
const compose = require('koa-compose');
const Router = require('koa-router');
const websocket = require('koa-easy-ws');
const error = require('koa-error');
const log4js = require('koa-log4');
const { koaBody } = require('koa-body');
const art = require('koa-art-template');
const staticCache = require('koa-static-cache');
const { LRUCache } = require('lru-cache');
const redis = require('redis');
const query = require('card-query');
const { Client: RpcClient } = require('@wbenxin/amqplib-rpc');
const Loader = require('./loader');
const default_config = require('../config.default');

module.exports = class Arche extends koa {
  constructor(props) {
    super(props);

    /* 初始化 */
    {
      // 加载器
      this.loader = new Loader(props);

      // 加载配置信息
      this.config = { ...default_config, ...this.loader.loadConfig() };
      // 加载控制器
      this.controller = this.loader.loadController();
      // 加载页面
      this.page = this.loader.loadViewPage();
      // 加载服务
      this.service = Object.assign(name => new this.service[name](this), this.loader.loadService());

      // 设置cookie密钥
      this.keys = this.config.cookieKeys;

      // 初始化日志配置
      log4js.configure(this.config.log4js);

      // 初始化关系数据库访问组件
      this.query = query;
      if (this.config.database) {
        query.configure(this.config.database);
      }

      // 记录错误日志
      this.on('error', (err, ctx) => {
        let logger = ctx.logger || console;
        logger.error(err.stack);
      });

      // 初始化模板引擎
      art(this, {
        root: path.join(this.loader.base, 'view'),
      });
    }

    /* 注册中间件 */
    {
      // 注册调用追踪中间件
      this.use(async (ctx, next) => {
        ctx.request.traceid = uuid.v4().replace(/-/g, '');
        await next();
      });
      // 注册访问日志记录中间件
      this.use(log4js.koaLogger(log4js.getLogger('access'), {
        tokens: [{
          token: ':traceid',
          content: ctx => ctx.request.traceid,
        }],
        format: '":method :url HTTP/:http-version" :status' +
          ' :remote-addr - :traceid - :content-length' +
          ' ":referrer" ":user-agent"'
      }));
      // 格式化服务端错误的响应
      this.use(error());

      // 注册静态资源文件处理中间件
      this.use(staticCache({
        dir: path.join(this.loader.base, 'static'),
        // static中放置永远不会修改的内容, 因此可以设置过期时间让浏览器直接使用本地缓存
        maxAge: 365 * 24 * 60 * 60,
        // 静态加载文件
        dynamic: false,
      }));

      // 注册动态资源文件处理中间件
      this.use(staticCache({
        dir: path.join(this.loader.base, 'view'),
        // 排除art模板文件和cjs服务端脚本
        filter: file => !file.toLowerCase().endsWith('.art') && !file.toLowerCase().endsWith('.cjs'),
        // 动态加载文件
        dynamic: true,
        // LRU缓存：最多2万个文件, 最多2小时
        files: new LRUCache({ max: 20000, ttl: 7200 * 1000 }),
      }));

      // 注册接口挂载中间件
      this.use(async (ctx, next) => {
        ctx.logger = log4js.getLogger('context');
        ctx.logger.addContext('traceid', ctx.request.traceid);

        ctx.service = this.service;

        // 挂载RPC访问接口
        if (this.config.rabbitmq) {
          ctx.rpc = new RpcClient(this.config.rabbitmq);
        }

        // 挂载Redis访问接口
        if (this.config.redis) {
          const s_redis = Symbol('redis');
          Object.defineProperty(ctx, "redis", {
            get: () => ctx[s_redis] = ctx[s_redis] || redis.createClient(this.config.redis)
          });
          await next();
          if (ctx[s_redis]) ctx[s_redis].quit();
        } else {
          await next();
        }
      });

      // 注册关系数据库访问中间件
      if (this.config.database) {
        this.use(query.middleware());
      }

      // 注册请求内容解析中间件
      this.use(koaBody(this.config.bodyparser));

      // 注册WebSocket协议中间件
      this.use(websocket('websocket'));
    }

    /* 在下个时间片挂载路由 */
    setImmediate(() => {
      const http_router = new Router();

      // 注册默认页面路由
      http_router.get(`/`, async (ctx, next) => {
        ctx.redirect('/index.html');
      });

      // 为所有页面注册路由
      Object.keys(this.page).forEach(key => {
        let ctrl_path = key.replace(/\./g, '/');
        http_router.all(`/${ctrl_path}`, async (ctx, next) => {
          ctx.logger.info(`▼ /${ctrl_path} - ${JSON.stringify(Object.assign({}, ctx.query, ctx.request.body))}`);
          const time = process.hrtime();
          const page = this.page[key];
          const fn = compose(Array.isArray(page) ? page : [page]);
          await fn(ctx, next);
          ctx.logger.info(`▲ /${ctrl_path} - ${process.hrtime(time).join('.')}`);
        });
      });

      // 为所有控制器注册路由
      Object.keys(this.controller).forEach(key => {
        const ctrl_path = key.replace(/\./g, '/');
        const __class = this.controller[key];
        if (typeof __class == 'function') {
          const inst = new __class();
          http_router.all(`/${ctrl_path}/:action/:append*`, async (ctx, next) => {
            const act = ctx.params.action ? inst[ctx.params.action] : undefined;
            if (!act) {
              ctx.status = 404;
            } else {
              ctx.logger.info(`▼ ${key}.${ctx.params.action} - ${JSON.stringify(Object.assign({}, ctx.query, ctx.request.body))}`);
              const time = process.hrtime();
              if (typeof act == 'function') {
                await act(ctx, next);
              } else {
                const fn = compose(act);
                await fn(ctx, next);
              }
              ctx.logger.info(`▲ ${key}.${ctx.params.action} - ${process.hrtime(time).join('.')}`);
            }
          });
        }
      });

      this.use(http_router.routes());
      this.use(http_router.allowedMethods());
    });
  }
}